﻿#region Header Licence
//  NKnobMan, Copyright 2009, Alexandre MUTEL.
// 
//  This is free software; you can redistribute it and/or modify it
//  under the terms of the GNU Lesser General Public License as
//  published by the Free Software Foundation; either version 2.1 of
//  the License, or (at your option) any later version.
// 
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//  Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public
//  License along with this software; if not, write to the Free
//  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
//  02110-1301 USA, or see the FSF site: http://www.fsf.org.
#endregion

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

namespace NKnobMan.Templatizer
{
    /// <summary>
    /// Interface to follow progress on generating knobs.
    /// </summary>
    public interface IProgressOnSaving
    {
        void InitProgress(string message, int maxStep);
        void Progress(string message, int step);
        void EndProgress();
    }

    [DataContract]
    public enum OutputAlignement
    {
        [EnumMember]
        Both,
        [EnumMember]
        Horizontal,
        [EnumMember]
        Vertical,
    }

    /// <summary>
    /// Knob Template Class.
    /// </summary>
    [DataContract]
    public class KnobTemplate : INotifyPropertyChanged
    {
        public const string DefaultNamePrefix = "Default";
        private BindingList<TemplateIteration> _iterations;
        private string _knobInputPath;
        private string _outputPath;
        private bool _saveIntermediateKnobs;
        private string _templateName;
        private bool _useKnobInputPathAsOutputPath;
        private bool _useRelativePathForKnobInput;
        private OutputAlignement _outputAlignement;

        public KnobTemplate()
        {
            _iterations = new BindingList<TemplateIteration>();
            Reset();
        }

        private string _filePath;

        /// <summary>
        /// Gets the current file path of this knob template
        /// </summary>
        /// <value>The file path.</value>
        public string FilePath
        {
            get { return _filePath; }
            private set
            {
                if (_filePath != value)
                {
                    _filePath = value;
                    OnPropertyChanged("FilePath");
                    OnPropertyChanged("AbsoluteOutputPath");
                }
            }
        }


        /// <summary>
        /// Gets or sets the name of the template.
        /// </summary>
        /// <value>The name of the template.</value>
        [DataMember(Order = 0)]
        public string TemplateName
        {
            get { return _templateName; }
            set { SetNotifyingProperty("TemplateName", ref _templateName, value); }
        }

        /// <summary>
        /// Gets or sets the knob input path.
        /// </summary>
        /// <value>The knob input path.</value>
        [DataMember(Order = 1)]
        public string KnobInputPath
        {
            get { return _knobInputPath; }
            set
            {
                SetNotifyingProperty("KnobInputPath", ref _knobInputPath, value);
                OnPropertyChanged("KnobPreview");
                OnPropertyChanged("AbsoluteOutputPath");
            }
        }

        /// <summary>
        /// Gets or sets the knob preview.
        /// </summary>
        /// <value>The knob preview.</value>
        public Bitmap KnobPreview
        {
            get
            {
                if (KnobInputPath != null)
                {
                    return ThumbnailHelper.GetThumbnail(AbsoluteKnobInputPath);
                }
                return null;
            }
            set { }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use knob input path as output path].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [use knob input path as output path]; otherwise, <c>false</c>.
        /// </value>
        [DataMember(Order = 2)]
        public bool UseKnobInputPathAsOutputPath
        {
            get { return _useKnobInputPathAsOutputPath; }
            set
            {
                SetNotifyingProperty("UseKnobInputPathAsOutputPath", ref _useKnobInputPathAsOutputPath, value);
                OnPropertyChanged("AbsoluteOutputPath");
            }
        }

        /// <summary>
        /// Gets or sets the output path.
        /// </summary>
        /// <value>The output path.</value>
        [DataMember(Order = 3)]
        public string OutputPath
        {
            get { return _outputPath; }
            set
            {
                SetNotifyingProperty("OutputPath", ref _outputPath, value);
                OnPropertyChanged("AbsoluteOutputPath");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [save intermediate knobs].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [save intermediate knobs]; otherwise, <c>false</c>.
        /// </value>
        [DataMember(Order = 4)]
        public bool SaveIntermediateKnobs
        {
            get { return _saveIntermediateKnobs; }
            set { SetNotifyingProperty("SaveIntermediateKnobs", ref _saveIntermediateKnobs, value); }
        }

        [DataMember(Order = 5)]
        public bool UseRelativePathForKnobInput
        {
            get { return _useRelativePathForKnobInput; }
            set { SetNotifyingProperty("UseRelativePathForKnobInput", ref _useRelativePathForKnobInput, value); }
        }

        [DataMember(Order = 6, EmitDefaultValue = false)]
        public OutputAlignement OutputAlignement
        {
            get { return _outputAlignement; }
            set { SetNotifyingProperty("OutputAlignement", ref _outputAlignement, value); }
        }

        /// <summary>
        /// Gets or sets the iterations.
        /// </summary>
        /// <value>The iterations.</value>
        [DataMember(Order = 7)]
        public BindingList<TemplateIteration> Iterations
        {
            get { return _iterations; }
            private set { _iterations = value; }
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public void Reset()
        {
            FilePath = null;
            KnobInputPath = null;
            OutputPath = null;
            TemplateName = DefaultNamePrefix;
            UseKnobInputPathAsOutputPath = true;
            SaveIntermediateKnobs = true;
            UseRelativePathForKnobInput = true;
            OutputAlignement = OutputAlignement.Both;
            Iterations.Clear();
        }

        /// <summary>
        /// Create a new Iteration for this Knob Template.
        /// </summary>
        /// <returns></returns>
        public TemplateIteration NewIteration()
        {
            int countNewName = 0;

            // Generate a default name
            bool isNameUsed;
            do
            {
                isNameUsed = false;
                countNewName++;
                foreach (TemplateIteration iteration in Iterations)
                {
                    if (iteration.Name == DefaultNamePrefix + countNewName)
                    {
                        isNameUsed = true;
                        break;
                    }
                }
            } while (isNameUsed);

            return new TemplateIteration(DefaultNamePrefix + countNewName, 0, 0);
        }

        /// <summary>
        /// Inits this with default operations.
        /// </summary>
        public void InitWithDefaultOperations()
        {
            Iterations.Clear();
            Iterations.Add(new TemplateIteration("Disable", IterationInput.Original, PreviewMouseEvent.Disable));
            Iterations.Add(new TemplateIteration("Normal", IterationInput.Original, PreviewMouseEvent.Normal));
            Iterations.Add(new TemplateIteration("Hot", IterationInput.Previous, PreviewMouseEvent.MouseHover));
            Iterations.Add(new TemplateIteration("Pressed", IterationInput.Previous, PreviewMouseEvent.MouseClick));
        }


        public string AbsoluteOutputPath
        {
            get
            {
                // Build output path
                string path = Path.GetDirectoryName(AbsoluteKnobInputPath);
                if (!UseKnobInputPathAsOutputPath)
                {
                    if (Path.IsPathRooted(OutputPath))
                    {
                        path = Path.GetDirectoryName(OutputPath);
                    }
                    else
                    {
                        path = Path.Combine(path, OutputPath);
                    }
                }
                return path;
            }
        }


        public Size KnobSize
        {
            get { return new Size(KnobMan.Default.PrefsOutputSizeX, KnobMan.Default.PrefsOutputSizeY); }
        }

            /// <summary>
        /// Generates the knobs
        /// </summary>
        /// <param name="progressOnSaving">The progress on saving.</param>
        public void Generate(IProgressOnSaving progressOnSaving)
        {
            KnobMan knobMan = KnobMan.Default;

            string path = AbsoluteOutputPath;

            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            progressOnSaving.InitProgress("Start generating " + Iterations.Count + " knobs from template",
                                          Iterations.Count);

            for (int i = 0; i < Iterations.Count; i++)
            {
                TemplateIteration iteration = Iterations[i];
                if (i == 0 || iteration.Input == IterationInput.Original)
                {
                    LoadKnobInput();
                }
                foreach (TemplateOperation operation in iteration.Operations)
                {
                    if (operation.Value != null)
                    {
                        string operationValue = operation.Value.ToString();
                        if (operation.Layer == (int) LayerEnum.All)
                        {
                            for (int layerId = 0; layerId < knobMan.Layers.Count; layerId++)
                            {
                                knobMan.Layers[layerId].SetValueFromPath(operation.Operation, operationValue);
                            }
                        }
                        else
                        {
                            knobMan.Layers[(int) operation.Layer - 1].SetValueFromPath(operation.Operation,
                                                                                       operationValue);
                        }
                    }
                }

                string prefixName = TemplateName + iteration.Name;
                if (SaveIntermediateKnobs)
                {
                    File.Delete(path + "\\" + prefixName + ".knob");
                    knobMan.SaveToFile(path + "\\" + prefixName + ".knob");
                }
                progressOnSaving.Progress("Export Knob [" + iteration.Name + "] from template", (i + 1));

                string exportImageFileName = path + "\\" + prefixName + ".png";

                // Delete the file before exporting
                File.Delete(exportImageFileName);

                switch (OutputAlignement)
                {
                    case Templatizer.OutputAlignement.Horizontal:
                        KnobMan.Default.PrefsAlign = 1;
                        break;
                    case Templatizer.OutputAlignement.Vertical:
                        KnobMan.Default.PrefsAlign = 0;
                        break;
                }

                if (OutputAlignement == OutputAlignement.Both)
                {
                    knobMan.ExportOptimizedImage(exportImageFileName);
                }
                else
                {
                    knobMan.ExportImage(exportImageFileName);
                }
                iteration.LoadBitmap(exportImageFileName, KnobSize);
            }
            LoadKnobInput();
            progressOnSaving.EndProgress();
        }

        /// <summary>
        /// Loads the knob input.
        /// </summary>
        public void LoadKnobInput()
        {
            KnobMan.Default.OpenFile(AbsoluteKnobInputPath);
            UpdateIterations();
        }

        private string AbsoluteKnobInputPath
        {
            get
            {
                string pathToKnobInput = KnobInputPath;
                if (UseRelativePathForKnobInput && FilePath != null && !Path.IsPathRooted(KnobInputPath))
                {
                    pathToKnobInput = Path.Combine(Path.GetDirectoryName(FilePath), KnobInputPath);
                }
                return pathToKnobInput;
            }
        }

        public Rectangle GetDrawBounds(Point centerLocation)
        {
            Rectangle bounds = Rectangle.Empty;
            Point leftTop = new Point(centerLocation.X - KnobSize.Width / 2,
                                      centerLocation.Y - KnobSize.Height / 2);

            bounds = new Rectangle(leftTop, KnobSize);
            return bounds;
        }


        /// <summary>
        /// Moves the specified iteration.
        /// </summary>
        /// <param name="iteration">The iteration.</param>
        /// <param name="index">The index.</param>
        public void Move(TemplateIteration iteration, int index)
        {
            _iterations.Remove(iteration);
            _iterations.Insert(index, iteration);
        }

        /// <summary>
        /// Loads the specified template. May throw an exception if file is invalid.
        /// </summary>
        /// <param name="filename">The filename.</param>
        public void Load(string filename)
        {
            KnobTemplate copy = null;
            DataContractSerializer ser = new DataContractSerializer(typeof (KnobTemplate));
            FileStream stream = new FileStream(filename, FileMode.Open);
            var writer = new XmlTextReader(new StreamReader(stream));
            copy = ser.ReadObject(writer) as KnobTemplate;
            stream.Close();
            if (copy != null)
            {
                FilePath = filename;
                KnobInputPath = copy.KnobInputPath;
                OutputPath = copy.OutputPath;
                TemplateName = copy.TemplateName;
                UseKnobInputPathAsOutputPath = copy.UseKnobInputPathAsOutputPath;
                SaveIntermediateKnobs = copy.SaveIntermediateKnobs;

                Iterations.Clear();
                foreach (TemplateIteration iteration in copy.Iterations)
                {
                    Iterations.Add(iteration);
                }
            }
        }

        private void UpdateKnobInputPathToRelative()
        {
            // Can transform KnobInputPath to a relative path only if:
            if (FilePath != null && KnobInputPath != null)
            {
                string templatePath = Path.GetDirectoryName(FilePath);
                string knobPath = KnobInputPath;

                if (UseRelativePathForKnobInput)
                {

                    // If KnobPath is already relative, set it back to absolute
                    if ( ! Path.IsPathRooted(knobPath) )
                    {
                        knobPath = Path.Combine(templatePath, knobPath);
                    }

                    try
                    {
                        KnobInputPath = PathHelper.RelativePath(templatePath, knobPath);
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, "Unable to transform path {0} to relative from {1}", knobPath, templatePath);
                    }
                } else
                {
                    if ( ! Path.IsPathRooted(knobPath) )
                    {
                        string newPath = Path.Combine(templatePath, knobPath);
                        KnobInputPath = newPath;
                    }                    
                }
            }
        }

        /// <summary>
        /// Saves this template to the specified filename.
        /// </summary>
        /// <param name="filename">The filename.</param>
        public void Save(string filename)
        {
            FilePath = filename; 
            UpdateKnobInputPathToRelative();
            DataContractSerializer ser = new DataContractSerializer(typeof (KnobTemplate));
            FileStream stream = new FileStream(filename, FileMode.Create);
            var writer = new XmlTextWriter(new StreamWriter(stream)) {Formatting = Formatting.Indented};
            ser.WriteObject(writer, this);
            writer.Flush();
            stream.Close();
        }

        /// <summary>
        /// Updates the iterations.
        /// </summary>
        private void UpdateIterations()
        {
            int layerCounts = KnobMan.Default.PrefsLayers;

            foreach (TemplateIteration iteration in Iterations)
            {
                BindingList<TemplateOperation> copyOps = new BindingList<TemplateOperation>(iteration.Operations);
                foreach (TemplateOperation operation in copyOps)
                {
                    if (((int) operation.Layer - 1) >= layerCounts)
                    {
                        iteration.Operations.Remove(operation);
                    }
                }
            }
        }
        
        #region INotifyPropertyChanged : see http://stackoverflow.com/questions/488587/whats-the-best-way-to-call-inotifypropertychangeds-propertychanged-event

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private void SetNotifyingProperty<T>(string propertyName,
                                             ref T field, T value)
        {
            if (value != null && value.Equals(field))
                return;
            field = value;
            OnPropertyChanged(propertyName);
        }

        #endregion
    }
}